package com.tanger.im;

import com.tanger.common.socket.NoticePushUser;
import com.tanger.common.utils.JSON;
import com.tanger.common.utils.MD5Util;
import com.tanger.common.utils.StringUtil;
import com.tanger.im.entity.ImUserRecord;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import javax.websocket.Session;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
public class IMUserCaches {

    /**
     * fuId -> session
     */
    private static Map<Long, Session> usm = new ConcurrentHashMap<>();

    /**
     * sid(sessionId) -> fuId
     */
    private static Map<String, Long> sum = new ConcurrentHashMap<>();

    /**
     * 用户信息 redis 缓存 key
     * TANGER:IM-PUSH:fuId
     */
    private static final String USER_KEY = "TANGER:IM-PUSH:%s";

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 获取所有用户Session
     * @return
     */
    public List<Session> findUsers() {
        List<NoticePushUser> pUsers = findAll();
        List<String> sIds = pUsers.stream().map(NoticePushUser::getSid).collect(Collectors.toList());

        List<Session> users = new ArrayList<>();
        Iterator<Map.Entry<String,Long>> sMap = sum.entrySet().iterator();
        List<Long> uIds = new ArrayList<>();
        while (sMap.hasNext()) {
            Map.Entry<String,Long> entry = sMap.next();
            if (sIds.stream().anyMatch(i -> i.equals(entry.getKey()))) {
                uIds.add(entry.getValue());
            }
        }

        if (CollectionUtils.isEmpty(uIds)) {
            return users;
        }

        Iterator<Map.Entry<Long,Session>> gMap = usm.entrySet().iterator();
        while (gMap.hasNext()) {
            Map.Entry<Long,Session> entry = gMap.next();
            if (uIds.stream().anyMatch(j -> j.equals(entry.getKey()))) {
                users.add(entry.getValue());
            }
        }
        return users;
    }

    /**
     * 获取所有用户信息
     * @return
     */
    public List<NoticePushUser> findAll() {
        Set<String> keys = stringRedisTemplate.keys(userKey("*"));
        return keys.stream()
                .map(this::findValue)
                .collect(Collectors.toList());
    }

    protected NoticePushUser findValue(String key) {
        if (!Strings.isEmpty(key)) {
            String data = stringRedisTemplate.opsForValue().get(key);
            return JSON.parse(data, NoticePushUser.class);
        }
        return null;
    }

    /**
     * 获取用户id
     * @param session
     * @return
     */
    public Long getUser(Session session) {
        // 获取用户信息
        Long uid = sum.get(session.getId());
        if (StringUtil.isNull(uid)) {
            return null;
        }
        String data = stringRedisTemplate.opsForValue().get(userKey(uid.toString()));
        if (StringUtil.isNull(data)) {
            return null;
        }
        NoticePushUser user = JSON.parse(data, NoticePushUser.class);
        return Objects.isNull(user)? null:user.getUid();
    }

    /**
     * 获取用户Session
     * @param uId
     * @return
     */
    public Session getUser(Long uId) {
        return usm.get(uId);
    }

    /**
     * 创建用户
     * @param session
     */
    public NoticePushUser create(Long fuId, Session session) {
        // 初始化用户信息
//        String uid = UUID.randomUUID().toString().replace("-", "");
        NoticePushUser user = new NoticePushUser(session.getId(), fuId);
        String se = JSON.stringify(user);
        // 缓存用户，session
        stringRedisTemplate.opsForValue().set(userKey(fuId.toString()), se, 24, TimeUnit.HOURS);
        usm.put(fuId, session);
        sum.put(session.getId(), fuId);
        return user;
    }

    /**
     * 删除用户
     * @param session
     */
    public NoticePushUser remove(Session session) {
        Long uid = sum.get(session.getId());
        if (!usm.containsKey(uid)) {
            return new NoticePushUser();
        }

        // 获取用户信息
        String data = stringRedisTemplate.opsForValue().get(userKey(uid.toString()));
        if (Strings.isEmpty(data)) {
            return new NoticePushUser();
        }
        NoticePushUser user = JSON.parse(data, NoticePushUser.class);

        // 移除用户缓存，session 缓存
        stringRedisTemplate.delete(userKey(uid.toString()));
        usm.remove(uid);
        sum.remove(session.getId(), uid);
        log.info("[im-push] on close connect; user: {}", uid);
        return user;
    }

    public String userKey(String fuId) {
        return String.format(USER_KEY, fuId);
    }

    /**
     * 保存离线消息
     * @param uId 接收用户ID
     * @param fId 发送用户ID
     * @param obj 消息内容 com.tanger.im.IMMessagePush
     *            格式:{"type":"text/voice/video/image/emote","content":"文本/url/表情字符","sendTime":"1703730942000",....}
     */
    public void saveOffLine(Long fId, Long uId, Integer mode, IMMessagePush obj) {
        try {
            ImUserRecord record = null;
            String key = String.format(CacheKey.IM_USER_OFF_RECORD,uId,fId);
            String cache = stringRedisTemplate.opsForValue().get(key);
            if (!StringUtil.isNull(cache)) {
                record = JSON.parse(cache,ImUserRecord.class);
                record.setOffLineTotal(record.getOffLineTotal()+1);
                record.setContent(JSON.stringify(obj));
                record.setLastTime(obj.getSendTime());
                stringRedisTemplate.opsForValue().set(key, JSON.stringify(record));
                return;
            }
            record = ImUserRecord.builder()
                    .fuId(fId).tuId(uId).offLineTotal(1).type(mode)
                    .md5Key(MD5Util.encode(fId.toString()+uId.toString())+"_"+mode)
                    .content(JSON.stringify(obj)).lastTime(obj.getSendTime())
                    .build();
            stringRedisTemplate.opsForValue().set(key, JSON.stringify(record));
        } catch (Exception e) {
            log.error("[im-push] save offLine message error", e);
        }
    }

    /**
     * 合并离线消息
     * @param uId 接收用户ID
     * @param records
     */
    public List<ImUserRecord> merge(Long uId, List<ImUserRecord> records) {
        try {
            String key = String.format(CacheKey.IM_USER_OFF_RECORD,uId,"*");
            //TODO 分组离线信息合并
            String groupKey = String.format(CacheKey.IM_USER_OFF_GROUP_RECORD,uId,"*");
            List<ImUserRecord> _records = findAll(key);

            List<Long> fuIds = records.stream().map(ImUserRecord::getFuId).collect(Collectors.toList());
            List<ImUserRecord> diffs = _records.stream()
                    .filter(item -> !fuIds.contains(item.getFuId()))
                    .collect(Collectors.toList());
            _records.removeAll(diffs);

            records = records.stream()
                    .map(r -> toMap(r, _records))
                    .collect(Collectors.toList());

            records.addAll(diffs);
        } catch (Exception e) {
            log.error("[im-push] save offLine message error", e);
        }
        stringRedisTemplate.opsForValue().set(String.format(CacheKey.IM_USER_RECORD, MD5Util.encode(uId.toString())),JSON.stringify(records));
        return records.stream()
                .sorted(Comparator.comparing(ImUserRecord::getLastTime).reversed())
                .collect(Collectors.toList());
    }

    private ImUserRecord toMap(ImUserRecord r, List<ImUserRecord> _rs) {
        if (_rs.stream().anyMatch(v -> v.getFuId().equals(r.getFuId()))) {
            ImUserRecord record = _rs.stream()
                    .filter(v -> v.getFuId().equals(r.getFuId()))
                    .findFirst()
                    .get();
            r.setContent(record.getContent());
            r.setLastTime(record.getLastTime());
            r.setOffLineTotal(record.getOffLineTotal());
        }
        return r;
    }

    /**
     * 获取用户离线消息
     * @return
     */
    public List<ImUserRecord> findAll(String key) {
        return Optional.ofNullable(stringRedisTemplate.keys(key))
                .map(ks -> ks.stream()
                        .map(this::findLineValue)
                        .collect(Collectors.toList()))
                .orElseGet(Collections::emptyList);
    }

    protected ImUserRecord findLineValue(String key) {
        if (!Strings.isEmpty(key)) {
            String data = stringRedisTemplate.opsForValue().get(key);
            return JSON.parse(data, ImUserRecord.class);
        }
        return null;
    }
}
